/*
    SDL - Simple DirectMedia Layer
    Partial implementation of SDL library (originally written by
    Sam Lantinga <slouken@libsdl.org>) for the particular purpose to support
    Prequengine (http://code.google.com/p/prequengine/) on BlackBerry(tm)
    tablets and smartphones.

    Copyright (C) 2013  xlamsp

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    xlamsp@gmail.com
*/


#include <pthread.h>
#include <bps/bps.h>
#include <bps/event.h>
#include <bps/navigator.h>
#include <bps/screen.h>
#include <bps/sensor.h>
#include <bps/virtualkeyboard.h>
#include <sys/keycodes.h>
#include <screen/screen.h>
#include <img/img.h>

#include "SDL_sysvideo.h"
#include "SDL_bbvideo.h"
#include "SDL_config.h"


#define BB_DRIVER_NAME "BlackBerry"

/*------------------------------------------------------------------------------
* Functions prototypes
------------------------------------------------------------------------------*/
static int BB_VideoInit(SDL_VideoDevice *device, SDL_PixelFormat *vformat);
static SDL_Surface *BB_SetVideoMode(SDL_VideoDevice *device, SDL_Surface *current,
                int width, int height, int bpp, Uint32 flags);
static void BB_UpdateRects(SDL_VideoDevice *device, int numrects, SDL_Rect *rects);
static void BB_VideoQuit(SDL_VideoDevice *device);
static void BB_PumpEvents(SDL_VideoDevice *device);
static int BB_AllocHWSurface(SDL_VideoDevice *device, SDL_Surface *surface);
static int BB_BlitHWSurface(SDL_VideoDevice *device,
                SDL_Surface *src, SDL_Rect *srcrect,
                SDL_Surface *dst, SDL_Rect *dstrect);
static void BB_FreeHWSurface(SDL_VideoDevice *device, SDL_Surface *surface);


/*------------------------------------------------------------------------------
* Helper functions
------------------------------------------------------------------------------*/

/* De-initialize the screen */
static void screen_quit(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;

    SDL_Log(DEBUG1, "entered");

    screen_destroy_window(hidden->screen_win);
    hidden->screen_win = NULL;

    screen_destroy_context(hidden->screen_ctx);
    hidden->screen_ctx = NULL;
}

/* Initialize the screen */
static int screen_init(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    int usage;
    int color;
    screen_buffer_t screen_buf;
    int screen_size[2];
    int buffer_size[4] = { 0, 0, 1, 1 };

    SDL_Log(DEBUG1, "entered");

    /* create screen context */
    if(screen_create_context(&(hidden->screen_ctx), SCREEN_APPLICATION_CONTEXT) != 0) {
        SDL_Log(ERROR, "failed to create screen context, err=%d %s",
            errno, strerror(errno));
        return -1;
    }

    /* create screen window */
    if(screen_create_window(&(hidden->screen_win), hidden->screen_ctx) != 0) {
        SDL_Log(ERROR, "failed to create screen window, err=%d %s",
            errno, strerror(errno));
        screen_destroy_context(hidden->screen_ctx);
        hidden->screen_ctx = NULL;
        return -1;
    }

    /* create screen group id to attach child windows */
    snprintf(hidden->screen_group_id, sizeof(hidden->screen_group_id), "%d", getpid());
    if(screen_create_window_group(hidden->screen_win, hidden->screen_group_id) != 0) {
        SDL_Log(ERROR, "failed to create screen group id=%s, err=%d %s",
            hidden->screen_group_id, errno, strerror(errno));
        screen_quit(device);
        return -1;
    }

    /* get screen window width, height and rotation */
    screen_get_window_property_iv(hidden->screen_win, SCREEN_PROPERTY_ROTATION, &(hidden->screen_rotation));
    screen_get_window_property_iv(hidden->screen_win, SCREEN_PROPERTY_SIZE, screen_size);
    hidden->screen_w = screen_size[0];
    hidden->screen_h = screen_size[1];
    if(hidden->screen_w <= 0 || hidden->screen_h <= 0 ||
        (hidden->screen_rotation != 0 && hidden->screen_rotation != 90 &&
        hidden->screen_rotation != 180 && hidden->screen_rotation != 270)) {
        SDL_Log(ERROR, "invalid resolution %dx%d or angle %d!",
            hidden->screen_w, hidden->screen_h, hidden->screen_rotation);
        screen_quit(device);
        return -1;
    }
    SDL_Log(INFO, "screen_win=0x%p screen size: %dx%d rotation=%d",
        hidden->screen_win, hidden->screen_w, hidden->screen_h, hidden->screen_rotation);

    /* set screen window usage type */
    usage = SCREEN_USAGE_NATIVE;
    screen_set_window_property_iv(hidden->screen_win, SCREEN_PROPERTY_USAGE, &usage);

    /* set screen window buffer parameters; set 1x1 buffer as it's not actually used */
    screen_set_window_property_iv(hidden->screen_win, SCREEN_PROPERTY_BUFFER_SIZE, buffer_size + 2);
    screen_set_window_property_iv(hidden->screen_win, SCREEN_PROPERTY_SOURCE_SIZE, screen_size);

    /* set screen window background color */
    color = 0xff000000; /* ARGB -> black */
    screen_set_window_property_iv(hidden->screen_win, SCREEN_PROPERTY_COLOR, &color);

    /* create screen window buffer */
    if(screen_create_window_buffers(hidden->screen_win, 1) != 0) {
        SDL_Log(ERROR, "failed to create screen window buffer, err=%d %s",
            errno, strerror(errno));
        screen_quit(device);
        return -1;
    }
    screen_get_window_property_pv(hidden->screen_win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&screen_buf);

    /* render screen window on the display */
    screen_post_window(hidden->screen_win, screen_buf, 1, buffer_size, 0);

    return 0;   /* success */
}

static void *app_window_thread(void *arg)
{
    SDL_VideoDevice *device = (SDL_VideoDevice*)arg;
    SDL_PrivateVideoData *hidden = device->hidden;
    app_window_t *app_win = &(hidden->app_win);
    int numrects = 1;
    int dirtyRects[4] = { 0, 0, device->info.current_w, device->info.current_h };
    int attribs[] = { SCREEN_BLIT_END };

    SDL_Log(DEBUG1, "entered, device=0x%p hidden=0x%p", device, hidden);

    pthread_mutex_lock(&(app_win->mutex));
    while(1) {
        if(app_win->thread_quit) {
            break;
        }

        if(app_win->upd_pend) {
            app_win->upd_pend = 0;

            if(device->screen && device->screen->hwdata) {
                screen_blit(hidden->screen_ctx, app_win->buf[app_win->cur_buf],
                    device->screen->hwdata->buffer, attribs);
                pthread_mutex_unlock(&(app_win->mutex));
                screen_post_window(app_win->win, app_win->buf[app_win->cur_buf], numrects, dirtyRects, 0);
                delay(10);
                pthread_mutex_lock(&(app_win->mutex));
                app_win->cur_buf ^= 1;
            }
        } else {
            pthread_cond_wait(&(app_win->cond), &(app_win->mutex));
        }
    }
    pthread_mutex_unlock(&(app_win->mutex));

    SDL_Log(DEBUG1, "exit");
    return NULL;
}

static void app_window_quit(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    app_window_t *app_win = &(hidden->app_win);

    SDL_Log(DEBUG1, "entered");

    if(app_win->thread) {
        app_win->thread_quit = 1;
        pthread_cond_signal(&(app_win->cond));
        pthread_join(app_win->thread, NULL);
        app_win->thread = 0;
        app_win->thread_quit = 0;
        pthread_mutex_destroy(&(app_win->mutex));
        pthread_cond_destroy(&(app_win->cond));
    }

    if(app_win->win) {
        screen_destroy_window(app_win->win);
        app_win->win = NULL;
    }

    SDL_Log(DEBUG1, "exit");
}

static int app_window_init(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    app_window_t *app_win = &(hidden->app_win);
    int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_NATIVE;
    int format = SCREEN_FORMAT_RGBA8888;
    int ret = 0;

    int pos[2];         /* position of the app window on the screen */
    int size[2];        /* window size, the picture will be stretched to fit */

    SDL_Log(DEBUG1, "entered");

    if((hidden->screen_w * 3) / 4 > hidden->screen_h) {
        /* landscape mode */
        device->info.current_h = hidden->screen_h;
        device->info.current_w = (hidden->screen_h * 4) / 3;
        pos[0] = (hidden->screen_w - device->info.current_w) / 2;
        pos[1] = 0;
    } else {
        /* portrait mode */
        device->info.current_w = hidden->screen_w;
        device->info.current_h = (hidden->screen_w * 3) / 4;
        pos[0] = 0;
        pos[1] = (hidden->screen_h - device->info.current_h) / 2;
    }
    size[0] = device->info.current_w;
    size[1] = device->info.current_h;
    app_win->kbd.edge_x_mid = device->info.current_w / 2;
    app_win->kbd.edge_y_top = device->info.current_h / 3;
    app_win->kbd.edge_y_btm = app_win->kbd.edge_y_top * 2;

    if(screen_create_window_type(&(app_win->win), hidden->screen_ctx, SCREEN_CHILD_WINDOW) == 0) {
        if(screen_join_window_group(app_win->win, hidden->screen_group_id) == 0) {
            ret |= screen_set_window_property_iv(app_win->win, SCREEN_PROPERTY_USAGE, &usage);
            ret |= screen_set_window_property_iv(app_win->win, SCREEN_PROPERTY_FORMAT, &format);
            ret |= screen_set_window_property_iv(app_win->win, SCREEN_PROPERTY_SIZE, size);
            ret |= screen_set_window_property_iv(app_win->win, SCREEN_PROPERTY_BUFFER_SIZE, size);
            ret |= screen_set_window_property_iv(app_win->win, SCREEN_PROPERTY_POSITION, pos);
            if(ret == 0) {
                if(screen_create_window_buffers(app_win->win, 2) == 0) {
                    ret |= screen_get_window_property_pv(app_win->win,
                        SCREEN_PROPERTY_RENDER_BUFFERS, (void **)(app_win->buf));
                    if(ret == 0) {
                        if(pthread_cond_init(&(app_win->cond), NULL) == 0) {
                            if(pthread_mutex_init(&(app_win->mutex), NULL) == 0) {
                                if(pthread_create(&(app_win->thread), NULL, app_window_thread, device) == 0) {
                                    SDL_Log(DEBUG1, "success, window=0x%p buf[0]=0x%p buf[1]=0x%p",
                                        app_win->win, app_win->buf[0], app_win->buf[1]);
                                    return 0;   /* sucess */
                                }
                                pthread_mutex_destroy(&(app_win->mutex));
                            }
                            pthread_cond_destroy(&(app_win->cond));
                        }
                    }
                    screen_destroy_window_buffers(app_win->win);
                }
            }
        }
        screen_destroy_window(app_win->win);
        app_win->win = NULL;
    }

    SDL_Log(ERROR, "failed");
    return -1;  /* fail */
}

/* De-initialize the virtual keyboard */
static void keyboard_quit(SDL_VideoDevice *device)
{
    vkbd_key_t *keyboard = device->hidden->keyboard;
    key_e key;

    SDL_Log(DEBUG1, "entered");

    for(key = E_KEY_MIN; key < E_KEY_TOTAL; key++) {
        if(keyboard[key].win) {
            screen_destroy_window(keyboard[key].win);
            keyboard[key].win = NULL;
        }
    }
}

/* Initialize the virtual keyboard */
static int keyboard_init(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    vkbd_key_t *keyboard = hidden->keyboard;
    key_e key;
    int key_x;
    int key_y;
    int key_w;
    int key_h;
    int i;
    int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_NATIVE;
    int format = SCREEN_FORMAT_RGBA8888;
    int attribs[] = { SCREEN_BLIT_COLOR, 0xff000000, SCREEN_BLIT_END };
    img_lib_t ilib;
    int ret;

    static const key_descr_t initializer[E_KEY_TOTAL] = {
        { SDLK_ESCAPE,  {"app/native/assets/button_up_esc.png",     "app/native/assets/button_dn_esc.png"}  },
        { SDLK_SPACE,   {"app/native/assets/button_up_space.png",   "app/native/assets/button_dn_space.png"}},
        { SDLK_RETURN,  {"app/native/assets/button_up_enter.png",   "app/native/assets/button_dn_enter.png"}},
        { SDLK_LSHIFT,  {"app/native/assets/button_up_shift.png",   "app/native/assets/button_dn_shift.png"}},
        { SDLK_LALT,    {"app/native/assets/button_up_alt.png",     "app/native/assets/button_dn_alt.png"}  },
        { SDLK_LCTRL,   {"app/native/assets/button_up_ctrl.png",    "app/native/assets/button_dn_ctrl.png"} },
        { SDLK_F1,      {"app/native/assets/button_up_f1.png",      "app/native/assets/button_dn_f1.png"}   },
        { SDLK_F2,      {"app/native/assets/button_up_f2.png",      "app/native/assets/button_dn_f2.png"}   },
        { SDLK_F3,      {"app/native/assets/button_up_f3.png",      "app/native/assets/button_dn_f3.png"}   },
        { SDLK_F4,      {"app/native/assets/button_up_f4.png",      "app/native/assets/button_dn_f4.png"}   },
        { SDLK_p,       {"app/native/assets/button_up_p.png",       "app/native/assets/button_dn_p.png"}    },
        { SDLK_h,       {"app/native/assets/button_up_h.png",       "app/native/assets/button_dn_h.png"}    },
        { SDLK_UP,      {NULL,                                      NULL}                                   },
        { SDLK_DOWN,    {NULL,                                      NULL}                                   },
        { SDLK_LEFT,    {NULL,                                      NULL}                                   },
        { SDLK_RIGHT,   {NULL,                                      NULL}                                   }
    };

    SDL_Log(DEBUG1, "entered");

    hidden->config.enable_accelerometer = 0; /* accelerometer is not enabled by default */

    /* common initialization */
    for(key = E_KEY_MIN; key < E_KEY_TOTAL; key++) {
        keyboard[key].win = NULL;
        keyboard[key].size[0] = 0; /* set 0 as an indication to not  */
        keyboard[key].size[1] = 0; /* create virtual button window */
        keyboard[key].hit = 0;
        keyboard[key].hit_cntr = 0;
        keyboard[key].sym = initializer[key].sym;
        keyboard[key].img_path[VKBD_BUTTON_UP] = initializer[key].img_path[VKBD_BUTTON_UP];
        keyboard[key].img_path[VKBD_BUTTON_DOWN] = initializer[key].img_path[VKBD_BUTTON_DOWN];
    }

    if((hidden->screen_w * 3) / 4 > hidden->screen_h) {
        /* landscape mode
        +-----+------------------------------+-----+
        | Esc |                              |Enter|
        +-----+                              +-----+
        |  F1 |                              |Shift|
        +-----+                              +-----+
        |  F2 |                              | Ctrl|
        +-----+      Application Window      +-----+
        |  F3 |                              |  P  |
        +-----+                              +-----+
        |  F4 |                              |  H  |
        +-----+                              +-----+
        | Alt |                              |Space|
        +-----+------------------------------+-----+
        */
        static const key_e left_bar[] = {
            E_KEY_ESC,
            E_KEY_F1,
            E_KEY_F2,
            E_KEY_F3,
            E_KEY_F4,
            E_KEY_ALT
        };
        static const key_e right_bar[] = {
            E_KEY_ENTER,
            E_KEY_SHIFT,
            E_KEY_CTRL,
            E_KEY_H,
            E_KEY_P,
            E_KEY_SPACE
        };
        int num_buttons;

        key_w = (hidden->screen_w - device->info.current_w) / 2;

        /* left bar */
        key_x = 0;
        key_y = 0;
        num_buttons = sizeof(left_bar) / sizeof(key_e);
        for(i = 0; i < num_buttons; i++) {
            key = left_bar[i];
            key_h = (hidden->screen_h * (i + 1)) / num_buttons - key_y;
            keyboard[key].pos[0] = key_x;
            keyboard[key].pos[1] = key_y;
            keyboard[key].size[0] = key_w;
            keyboard[key].size[1] = key_h;
            key_y += key_h;
        }

        /* right bar */
        key_x = key_w + device->info.current_w;
        key_y = 0;
        num_buttons = sizeof(right_bar) / sizeof(key_e);
        for(i = 0; i < num_buttons; i++) {
            key = right_bar[i];
            key_h = (hidden->screen_h * (i + 1)) / num_buttons - key_y;
            keyboard[key].pos[0] = key_x;
            keyboard[key].pos[1] = key_y;
            keyboard[key].size[0] = key_w;
            keyboard[key].size[1] = key_h;
            key_y += key_h;
        }
    } else {
        /* portrait mode
        +-----+-----+-----+-----+-----+-----+
        | Esc |  F1 |  F2 |  F3 |  F4 |Enter|
        +-----+-----+-----+-----+-----+-----+
        |                                   |
        |                                   |
        |                                   |
        |                                   |
        |                                   |
        |         Application Window        |
        |                                   |
        |                                   |
        |                                   |
        |                                   |
        |                                   |
        +-----+-----+-----+-----+-----+-----+
        | Alt |Shift| Ctrl|  H  |  P  |Space|
        +-----+-----+-----+-----+-----+-----+
        */
        static const key_e top_bar[] = {
            E_KEY_ESC,
            E_KEY_F1,
            E_KEY_F2,
            E_KEY_F3,
            E_KEY_F4,
            E_KEY_ENTER

        };
        static const key_e bottom_bar[] = {
            E_KEY_ALT,
            E_KEY_SHIFT,
            E_KEY_CTRL,
            E_KEY_H,
            E_KEY_P,
            E_KEY_SPACE
        };
        int num_buttons;

        key_h = (hidden->screen_h - device->info.current_h) / 2;

        /* top bar */
        key_x = 0;
        key_y = 0;
        num_buttons = sizeof(top_bar) / sizeof(key_e);
        for(i = 0; i < num_buttons; i++) {
            key = top_bar[i];
            key_w = (hidden->screen_w * (i + 1)) / num_buttons - key_x;
            keyboard[key].pos[0] = key_x;
            keyboard[key].pos[1] = key_y;
            keyboard[key].size[0] = key_w;
            keyboard[key].size[1] = key_h;
            key_x += key_w;
        }

        /* bottom bar */
        key_x = 0;
        key_y = key_h + device->info.current_h;
        num_buttons = sizeof(bottom_bar) / sizeof(key_e);
        for(i = 0; i < num_buttons; i++) {
            key = bottom_bar[i];
            key_w = (hidden->screen_w * (i + 1)) / num_buttons - key_x;
            keyboard[key].pos[0] = key_x;
            keyboard[key].pos[1] = key_y;
            keyboard[key].size[0] = key_w;
            keyboard[key].size[1] = key_h;
            key_x += key_w;
        }
    }

    if((ret = img_lib_attach(&ilib)) != IMG_ERR_OK) {
        SDL_Log(ERROR, "failed to init image library, err=%d", ret);
        return -1;
    }

    for(key = E_KEY_MIN; key < E_KEY_TOTAL; key++) {
        if(keyboard[key].size[0] && keyboard[key].size[1]) {
            screen_create_window_type(&(keyboard[key].win), hidden->screen_ctx, SCREEN_CHILD_WINDOW);
            SDL_Log(DEBUG1, "win=0x%p button %d at %d,%d size %dx%d",
                keyboard[key].win, key,
                keyboard[key].pos[0], keyboard[key].pos[1],
                keyboard[key].size[0], keyboard[key].size[1]);

            screen_join_window_group(keyboard[key].win, hidden->screen_group_id);

            screen_set_window_property_iv(keyboard[key].win, SCREEN_PROPERTY_USAGE, &usage);
            screen_set_window_property_iv(keyboard[key].win, SCREEN_PROPERTY_FORMAT, &format);
            screen_set_window_property_iv(keyboard[key].win, SCREEN_PROPERTY_SIZE, keyboard[key].size);
            screen_set_window_property_iv(keyboard[key].win, SCREEN_PROPERTY_POSITION, keyboard[key].pos);
            screen_set_window_property_iv(keyboard[key].win, SCREEN_PROPERTY_BUFFER_SIZE, keyboard[key].size);
            screen_set_window_property_iv(keyboard[key].win, SCREEN_PROPERTY_SOURCE_SIZE, keyboard[key].size);

            screen_create_window_buffers(keyboard[key].win, 2);
            screen_get_window_property_pv(keyboard[key].win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)(keyboard[key].buf));

            for(i = VKBD_BUTTON_UP; i <= VKBD_BUTTON_DOWN; i++) {
                img_t img;
                unsigned char *ptr;
                int stride;

                screen_get_buffer_property_pv(keyboard[key].buf[i], SCREEN_PROPERTY_POINTER, (void **)&ptr);
                screen_get_buffer_property_iv(keyboard[key].buf[i], SCREEN_PROPERTY_STRIDE, &stride);
                SDL_Log(DEBUG1, "key=%d state=%d ptr=0x%p stride=%d", key, i, ptr, stride);

                img.format = IMG_FMT_PKHE_ARGB8888;
                img.w = keyboard[key].size[0];
                img.h = keyboard[key].size[1];
                img.access.direct.data = ptr;
                img.access.direct.stride = stride;
                img.flags = IMG_FORMAT | IMG_DIRECT | IMG_W | IMG_H;

                if((ret = img_load_resize_file(ilib, keyboard[key].img_path[i], NULL, &img)) != IMG_ERR_OK) {
                    SDL_Log(ERROR, "failed to load %s, err=%d", keyboard[key].img_path[i], ret);
                    attribs[1] = (i == VKBD_BUTTON_UP ? 0xff0000ff : 0xffffff00);
                    screen_fill(hidden->screen_ctx, keyboard[key].buf[i], attribs);
                }
            }

            int rect[4] = { 0, 0, keyboard[key].size[0], keyboard[key].size[1] };
            screen_post_window(keyboard[key].win, keyboard[key].buf[VKBD_BUTTON_UP], 1, rect, 0);
        }
    }

    img_lib_detach(ilib);
    return 0;
}

/* Store SDL event in the internal queue */
static void event_add(SDL_VideoDevice *device, SDL_Event *event)
{
    events_t *events = &(device->hidden->events);
    int tail;

    pthread_mutex_lock(&(events->mutex));
    tail = (events->tail + 1) % events->queue_size;
    if(tail == events->head) {
        /* Queue overflow */
        if(event->type == SDL_QUIT) {
            /* SDL_QUIT is high priority, if no space then replace the previous event */
            SDL_Log(WARNING, "SDL_QUIT replaces the previous event %u",
                (Uint32)(events->queue[events->tail].type));
            events->queue[events->tail] = *event;
        } else {
            /* Drop event */
            SDL_Log(WARNING, "dropped event %u", (Uint32)(event->type));
        }
    } else {
        events->queue[events->tail] = *event;
        events->tail = tail;
    }
    pthread_mutex_unlock(&(events->mutex));
}

/* Manage keyboard-related events from different sources
   (screen, accelerometer, physycal keyboard) */
static void events_keyboard_manager(SDL_VideoDevice *device, key_e key, int state)
{
    vkbd_key_t *keyboard = device->hidden->keyboard;
    SDL_Event event;
    int rect[4] = { 0, 0, 0, 0 };
    Uint8 *keys = SDL_GetKeyState(NULL);

    if(state == SDL_PRESSED) {
        if(keyboard[key].hit_cntr == 0) {
            // send key press
            SDL_Log(DEBUG2, "key %d pressed", key);

            event.type = SDL_KEYDOWN;
            event.key.state = SDL_PRESSED;
            event.key.keysym.sym = keyboard[key].sym;
            keys[keyboard[key].sym] = 1;
            event_add(device, &event);
            rect[2] = keyboard[key].size[0];
            rect[3] = keyboard[key].size[1];
            screen_post_window(keyboard[key].win, keyboard[key].buf[VKBD_BUTTON_DOWN], 1, rect, 0);
        }
        keyboard[key].hit_cntr++;
    } else {
        if(keyboard[key].hit_cntr > 0) {
            keyboard[key].hit_cntr--;
            if(keyboard[key].hit_cntr == 0) {
                // send key release
                SDL_Log(DEBUG2, "key %d released", key);

                event.type = SDL_KEYUP;
                event.key.state = SDL_RELEASED;
                event.key.keysym.sym = keyboard[key].sym;
                keys[keyboard[key].sym] = 0;
                event_add(device, &event);
                rect[2] = keyboard[key].size[0];
                rect[3] = keyboard[key].size[1];
                screen_post_window(keyboard[key].win, keyboard[key].buf[VKBD_BUTTON_UP], 1, rect, 0);
            }
        } else {
            SDL_Log(WARNING, "key %d unmatched release", key);
        }
    }
}

/* Convert keyboard scan code to one or two keys */
static int scan2keys(int scan, key_e *keys)
{
    int num_keys;

    switch (scan) {
        case SCAN_CODE_ALT:
            num_keys = 1;
            keys[0] = E_KEY_ALT;
            break;

        case SCAN_CODE_SYM:
            num_keys = 1;
            keys[0] = E_KEY_CTRL;
            break;

        case SCAN_CODE_SPACE:
            num_keys = 1;
            keys[0] = E_KEY_SPACE;
            break;

        case SCAN_CODE_LSHIFT:
        case SCAN_CODE_RSHIFT:
            num_keys = 1;
            keys[0] = E_KEY_SHIFT;
            break;

        case SCAN_CODE_ENTER:
        case SCAN_CODE_D:
            num_keys = 1;
            keys[0] = E_KEY_ENTER;
            break;

        case SCAN_CODE_H:
            num_keys = 1;
            keys[0] = E_KEY_H;
            break;

        case SCAN_CODE_P:
            num_keys = 1;
            keys[0] = E_KEY_P;
            break;

        case SCAN_CODE_E:
            num_keys = 1;
            keys[0] = E_KEY_UP;
            break;

        case SCAN_CODE_X:
            num_keys = 1;
            keys[0] = E_KEY_DOWN;
            break;

        case SCAN_CODE_S:
            num_keys = 1;
            keys[0] = E_KEY_LEFT;
            break;

        case SCAN_CODE_F:
            num_keys = 1;
            keys[0] = E_KEY_RIGHT;
            break;

        case SCAN_CODE_W:
            num_keys = 2;
            keys[0] = E_KEY_UP;
            keys[1] = E_KEY_LEFT;
            break;

        case SCAN_CODE_R:
            num_keys = 2;
            keys[0] = E_KEY_UP;
            keys[1] = E_KEY_RIGHT;
            break;

        case SCAN_CODE_Z:
            num_keys = 2;
            keys[0] = E_KEY_DOWN;
            keys[1] = E_KEY_LEFT;
            break;

        case SCAN_CODE_C:
            num_keys = 2;
            keys[0] = E_KEY_DOWN;
            keys[1] = E_KEY_RIGHT;
            break;

        default:
            num_keys = 0;
    }

    return num_keys;
}

/* Handle keyboard-originated events */
static void events_handle_keyboard(SDL_VideoDevice *device, screen_event_t screen_event)
{
    int num_keys;
    key_e keys[2];
    int flags;
    int scan;
    int i;

    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_FLAGS, &flags);
    if(flags & KEY_SCAN_VALID) {
        screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_SCAN, &scan);
        if(flags & KEY_DOWN) {
            if(!(flags & KEY_REPEAT)) {
                num_keys = scan2keys(scan, keys);
                for(i = 0; i < num_keys; i++) {
                    events_keyboard_manager(device, keys[i], SDL_PRESSED);
                }
            }
        } else {
            num_keys = scan2keys(scan, keys);
            for(i = 0; i < num_keys; i++) {
                events_keyboard_manager(device, keys[i], SDL_RELEASED);
            }
        }
    }
}

static void events_handle_app_win(SDL_VideoDevice *device, screen_event_t screen_event)
{
    app_win_kbd_t *app_kbd = &(device->hidden->app_win.kbd);
    int type;
    int pos[2];
    int touch_id;
    key_e key;
    int buttons;
    app_win_x_pos_e x_pos;
    app_win_y_pos_e y_pos;

    /* Application window sensitivity zones
    +-----------------------------------------------------+ ---------
    |                                                     |   ^    ^
    |                      Button Up                      |   |    |
    |                                                     |   | edge_y_top
    |-----------------------------------------------------|   v    |
    |/////////// margin zone (for de-bouncing) ///////////| ----   |
    |------------------------+///+------------------------|        | edge_y_btm
    |                        |///|                        |        |
    |       Button Left      |///|      Button Right      |        |
    |                        |///|                        |   |    |
    |------------------------+///+------------------------|   v    v
    |/////////////////////////////////////////////////////| ----------
    |-----------------------------------------------------| ----
    |                                                     |   ^
    |                     Button Down                     |   | MARGIN_WIDTH
    |                                                     |
    +-----------------------------------------------------+

    |        edge_x_mid        |
    |<------------------------>|
    */

    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TOUCH_ID, &touch_id);
    if(touch_id >= TOUCH_ID_MAX) {
        SDL_Log(WARNING, "too many simultaneous touches, ignore event %d touch_id=%d",
            type, touch_id);
        return;
    }

    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &type);
    switch (type) {
        case SCREEN_EVENT_MTOUCH_TOUCH:
            screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pos);

            if(pos[0] < app_kbd->edge_x_mid) {
                /* button left */
                app_kbd->touch_scr[touch_id].x_pos = E_X_LEFT;
                key = E_KEY_LEFT;
            } else {
                /* button right */
                app_kbd->touch_scr[touch_id].x_pos = E_X_RIGHT;
                key = E_KEY_RIGHT;
            }

            if(pos[1] < app_kbd->edge_y_top) {
                /* button up */
                app_kbd->touch_scr[touch_id].y_pos = E_Y_UP;
                key = E_KEY_UP;
            } else if(pos[1] >= app_kbd->edge_y_btm) {
                /* button down */
                app_kbd->touch_scr[touch_id].y_pos = E_Y_DOWN;
                key = E_KEY_DOWN;
            } else {
                /* middle section */
                app_kbd->touch_scr[touch_id].y_pos = E_Y_MID;
            }

            app_kbd->touch_scr[touch_id].key = key;
            events_keyboard_manager(device, key, SDL_PRESSED);
            break;

        case SCREEN_EVENT_MTOUCH_MOVE:
            screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pos);

            if(pos[0] < app_kbd->edge_x_mid - MARGIN_WIDTH) {
                /* button left */
                x_pos = E_X_LEFT;
                key = E_KEY_LEFT;
            } else if(pos[0] >= app_kbd->edge_x_mid + MARGIN_WIDTH) {
                /* button right */
                x_pos = E_X_RIGHT;
                key = E_KEY_RIGHT;
            } else {
                /* margin zone */
                x_pos = E_X_INVALID;
            }

            if(pos[1] < app_kbd->edge_y_top - MARGIN_WIDTH) {
                /* button up */
                y_pos = E_Y_UP;
                key = E_KEY_UP;
            } else if(pos[1] >= app_kbd->edge_y_btm + MARGIN_WIDTH) {
                /* button down */
                y_pos = E_Y_DOWN;
                key = E_KEY_DOWN;
            } else if(pos[1] >= app_kbd->edge_y_top + MARGIN_WIDTH &&
                      pos[1] < app_kbd->edge_y_btm - MARGIN_WIDTH) {
                /* middle section */
                y_pos = E_Y_MID;
            } else {
                /* margin zone */
                y_pos = E_Y_INVALID;
            }

            if(x_pos != E_X_INVALID && y_pos != E_Y_INVALID) {
                if(app_kbd->touch_scr[touch_id].key != key) {
                    events_keyboard_manager(device, app_kbd->touch_scr[touch_id].key, SDL_RELEASED);
                    events_keyboard_manager(device, key, SDL_PRESSED);
                    app_kbd->touch_scr[touch_id].x_pos = x_pos;
                    app_kbd->touch_scr[touch_id].y_pos = y_pos;
                    app_kbd->touch_scr[touch_id].key = key;
                }
            }
            break;

        case SCREEN_EVENT_MTOUCH_RELEASE:
            events_keyboard_manager(device, app_kbd->touch_scr[touch_id].key, SDL_RELEASED);
            break;

        case SCREEN_EVENT_POINTER:
            screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS, &buttons);
            if(buttons & SCREEN_LEFT_MOUSE_BUTTON) {
                /* mouse button pressed */
                screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pos);
                if(app_kbd->pointer.x_pos == E_X_INVALID && app_kbd->pointer.y_pos == E_Y_INVALID) {
                    /* mouse button previously was released */
                    if(pos[0] < app_kbd->edge_x_mid) {
                        /* button left */
                        app_kbd->pointer.x_pos = E_X_LEFT;
                        key = E_KEY_LEFT;
                    } else {
                        /* button right */
                        app_kbd->pointer.x_pos = E_X_RIGHT;
                        key = E_KEY_RIGHT;
                    }

                    if(pos[1] < app_kbd->edge_y_top) {
                        /* button up */
                        app_kbd->pointer.y_pos = E_Y_UP;
                        key = E_KEY_UP;
                    } else if(pos[1] >= app_kbd->edge_y_btm) {
                        /* button down */
                        app_kbd->pointer.y_pos = E_Y_DOWN;
                        key = E_KEY_DOWN;
                    } else {
                        /* middle section */
                        app_kbd->pointer.y_pos = E_Y_MID;
                    }

                    app_kbd->pointer.key = key;
                    events_keyboard_manager(device, key, SDL_PRESSED);
                } else {
                    /* mouse button previously was pressed */
                    if(pos[0] < app_kbd->edge_x_mid - MARGIN_WIDTH) {
                        /* button left */
                        x_pos = E_X_LEFT;
                        key = E_KEY_LEFT;
                    } else if(pos[0] >= app_kbd->edge_x_mid + MARGIN_WIDTH) {
                        /* button right */
                        x_pos = E_X_RIGHT;
                        key = E_KEY_RIGHT;
                    } else {
                        /* margin zone */
                        x_pos = E_X_INVALID;
                    }

                    if(pos[1] < app_kbd->edge_y_top - MARGIN_WIDTH) {
                        /* button up */
                        y_pos = E_Y_UP;
                        key = E_KEY_UP;
                    } else if(pos[1] >= app_kbd->edge_y_btm + MARGIN_WIDTH) {
                        /* button down */
                        y_pos = E_Y_DOWN;
                        key = E_KEY_DOWN;
                    } else if(pos[1] >= app_kbd->edge_y_top + MARGIN_WIDTH &&
                              pos[1] < app_kbd->edge_y_btm - MARGIN_WIDTH) {
                        /* middle section */
                        y_pos = E_Y_MID;
                    } else {
                        /* margin zone */
                        y_pos = E_Y_INVALID;
                    }

                    if(x_pos != E_X_INVALID && y_pos != E_Y_INVALID) {
                        if(app_kbd->pointer.key != key) {
                            events_keyboard_manager(device, app_kbd->pointer.key, SDL_RELEASED);
                            events_keyboard_manager(device, key, SDL_PRESSED);
                            app_kbd->pointer.x_pos = x_pos;
                            app_kbd->pointer.y_pos = y_pos;
                            app_kbd->pointer.key = key;
                        }
                    }
                    break;
                }
            } else {
                /* mouse button released */
                if(app_kbd->pointer.x_pos != E_X_INVALID && app_kbd->pointer.y_pos != E_Y_INVALID) {
                    events_keyboard_manager(device, app_kbd->pointer.key, SDL_RELEASED);
                    app_kbd->pointer.x_pos = E_X_INVALID;
                    app_kbd->pointer.y_pos = E_Y_INVALID;
                }
            }
            break;
    } /* switch */
}

/* Handle screen-originated events */
static void events_handle_screen(SDL_VideoDevice *device, bps_event_t *event)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    vkbd_key_t *keyboard = hidden->keyboard;
    key_e key;
    int type;
    int buttons;
    screen_window_t window;
    screen_event_t screen_event = screen_event_get_event(event);

    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &type);
    switch (type) {
        case SCREEN_EVENT_KEYBOARD:
            events_handle_keyboard(device, screen_event);
            break;

        case SCREEN_EVENT_MTOUCH_TOUCH:
        case SCREEN_EVENT_MTOUCH_RELEASE:
            screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void **)&window);
            if(window == hidden->app_win.win) {
                events_handle_app_win(device, screen_event);
            } else {
                for(key = E_KEY_MIN; key < E_KEY_TOTAL; key++) {
                    if(window == keyboard[key].win) {
                        if(type == SCREEN_EVENT_MTOUCH_TOUCH) {
                            events_keyboard_manager(device, key, SDL_PRESSED);
                        } else {
                            events_keyboard_manager(device, key, SDL_RELEASED);
                        }
                        break; /* for() */
                    }
                }
            }
            break; /* case: */

        case SCREEN_EVENT_POINTER:
            screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void **)&window);
            if(window == hidden->app_win.win) {
                events_handle_app_win(device, screen_event);
            } else {
                screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS, &buttons);
                for(key = 0; key < E_KEY_TOTAL; key++) {
                    if(window == keyboard[key].win) {
                        if(buttons & SCREEN_LEFT_MOUSE_BUTTON) {
                            if(!(keyboard[key].hit & E_HIT_EVENT_POINTER)) {
                                /* send key press */
                                keyboard[key].hit |= E_HIT_EVENT_POINTER;
                                events_keyboard_manager(device, key, SDL_PRESSED);
                            }
                        } else {
                            if(keyboard[key].hit & E_HIT_EVENT_POINTER) {
                                /* send key release */
                                keyboard[key].hit &= ~E_HIT_EVENT_POINTER;
                                events_keyboard_manager(device, key, SDL_RELEASED);
                            }
                        }
                    } else {
                        if(keyboard[key].hit & E_HIT_EVENT_POINTER) {
                            /* send key release */
                            keyboard[key].hit &= ~E_HIT_EVENT_POINTER;
                            events_keyboard_manager(device, key, SDL_RELEASED);
                        }
                    }
                }
            }
            break;

        case SCREEN_EVENT_MTOUCH_MOVE:
            screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void **)&window);
            if(window == hidden->app_win.win) {
                events_handle_app_win(device, screen_event);
            }
            break;

        case SCREEN_EVENT_PROPERTY:
        case SCREEN_EVENT_POST:
            /* ignore event */
            break;

        default:
            SDL_Log(DEBUG2, "SCREEN unhandled event: %d", type);
    }
}

/* Handle navigator-originated events */
static void events_handle_navigator(SDL_VideoDevice *device, bps_event_t *event)
{
    int code = bps_event_get_code(event);

    switch (code) {
        SDL_Event event;
        case NAVIGATOR_SWIPE_DOWN:
            SDL_Log(INFO, "NAVIGATOR_SWIPE_DOWN event (0x%x)", NAVIGATOR_SWIPE_DOWN);
            event.type = SDL_QUIT;
            event_add(device, &event);
            break;

        case NAVIGATOR_EXIT:
            SDL_Log(INFO, "NAVIGATOR_EXIT event (0x%x)", NAVIGATOR_EXIT);
            event.type = SDL_QUIT;
            event_add(device, &event);
            break;

        default:
            SDL_Log(DEBUG2, "NAVIGATOR unhandled event (0x%x)", code);
    }
}

/* Handle accelerometer-originated events */
static void events_handle_accelerometer(SDL_VideoDevice *device, bps_event_t *event)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    vkbd_key_t *keyboard = hidden->keyboard;
    float force_x, force_y, force_z;
    float roll;
    static const float threshold_dn = 1.700; /* when roll goes above the threshold the button considered pressed */
    static const float threshold_up = 1.600; /* when roll goes below the threshold the button considered released */

    sensor_event_get_xyz(event, &force_x, &force_y, &force_z);

    switch (hidden->screen_rotation) {
        case 0:
            roll = force_x;
            break;

        case 90:
            roll = -force_y;
            break;

        case 180:
            roll = -force_x;
            break;

        default: /* 270 */
            roll = force_y;
    }

    if(keyboard[E_KEY_LEFT].hit & E_HIT_ACCELEROMETER) {
        /* Left button is currently pressed */
        if(roll < threshold_up) {
            /* send key release */
            keyboard[E_KEY_LEFT].hit &= ~E_HIT_ACCELEROMETER;
            events_keyboard_manager(device, E_KEY_LEFT, SDL_RELEASED);
        }
    } else {
        /* Left button is currently released */
        if(roll > threshold_dn) {
            /* send key press */
            keyboard[E_KEY_LEFT].hit |= E_HIT_ACCELEROMETER;
            events_keyboard_manager(device, E_KEY_LEFT, SDL_PRESSED);
        }
    }

    if(keyboard[E_KEY_RIGHT].hit & E_HIT_ACCELEROMETER) {
        /* Right button is currently pressed */
        if(roll > -threshold_up) {
            /* send key release */
            keyboard[E_KEY_RIGHT].hit &= ~E_HIT_ACCELEROMETER;
            events_keyboard_manager(device, E_KEY_RIGHT, SDL_RELEASED);
        }
    } else {
        /* Right button is currently released */
        if(roll < -threshold_dn) {
            /* send key press */
            keyboard[E_KEY_RIGHT].hit |= E_HIT_ACCELEROMETER;
            events_keyboard_manager(device, E_KEY_RIGHT, SDL_PRESSED);
        }
    }
}

/* Sort out events from the different sensors */
static void events_handle_sensor(SDL_VideoDevice *device, bps_event_t *event)
{
    int code = bps_event_get_code(event);

    switch (code) {
        case SENSOR_ACCELEROMETER_READING:
            events_handle_accelerometer(device, event);
            break;

        default:
            SDL_Log(DEBUG2, "event from unknown sensor 0x%x", code);
    }
}

/* Thread to poll internal events */
static void *events_thread(void *arg)
{
    SDL_VideoDevice *device = (SDL_VideoDevice*)arg;
    SDL_PrivateVideoData *hidden = device->hidden;
    events_t *events = &(hidden->events);
    bps_event_t *event = NULL;

    SDL_Log(DEBUG1, "entered, device=0x%p hidden=0x%p", device, hidden);

    if(bps_initialize() != BPS_SUCCESS) {
        SDL_Log(ERROR, "failed to initialize BPS, err=%d %s",
            errno, strerror(errno));
        return NULL;
    }

    /* subscribe to screen events */
    if(screen_request_events(hidden->screen_ctx) != BPS_SUCCESS) {
        SDL_Log(WARNING, "failed to subscribe on screen events, err=%d %s",
            errno, strerror(errno));
    }

    /* subscribe to keyboard events */
    if(virtualkeyboard_request_events(0) != BPS_SUCCESS) {
        SDL_Log(WARNING, "failed to subscribe on keyboard events, err=%d %s",
            errno, strerror(errno));
    }

    /* subscribe to navigator events */
    if(navigator_request_events(0) != BPS_SUCCESS) {
        SDL_Log(WARNING, "failed to subscribe on navigator events, err=%d %s",
            errno, strerror(errno));
    }

    /* subscribe to accelerometer events */
    if(hidden->config.enable_accelerometer && sensor_is_supported(SENSOR_TYPE_ACCELEROMETER)) {
        sensor_set_rate(SENSOR_TYPE_ACCELEROMETER, 200000); /* 5 Hz */
        sensor_set_skip_duplicates(SENSOR_TYPE_ACCELEROMETER, true);
        if(sensor_request_events(SENSOR_TYPE_ACCELEROMETER) != BPS_SUCCESS) {
            SDL_Log(WARNING, "failed to subscribe on accelerometer events, err=%d %s",
                errno, strerror(errno));
        }
    }

    while(!events->thread_quit) {
        if(bps_get_event(&event, 100) != BPS_SUCCESS) {
            SDL_Log(WARNING, "failed bps_get_event, err=%d %s",
                errno, strerror(errno));
            break;
        }

        if(event) {
            int domain = bps_event_get_domain(event);

            if (domain == screen_get_domain()) {
                events_handle_screen(device, event);
            } else if (domain == navigator_get_domain()) {
                events_handle_navigator(device, event);
            } else if (domain == sensor_get_domain()) {
                events_handle_sensor(device, event);
            } else {
                SDL_Log(WARNING, "event from unknown domain %d!", domain);
            }
        }
    }

    if(hidden->config.enable_accelerometer && sensor_is_supported(SENSOR_TYPE_ACCELEROMETER)) {
        sensor_stop_events(SENSOR_TYPE_ACCELEROMETER);
    }
    screen_stop_events(hidden->screen_ctx);
    bps_shutdown();

    SDL_Log(DEBUG1, "exit");
    return NULL;
}

/* De-initialize the events sources */
static void events_quit(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    events_t *events = &(hidden->events);

    SDL_Log(DEBUG1, "entered");

    if(events->thread) {
        events->thread_quit = 1;
        pthread_join(events->thread, NULL);
        events->thread = 0;
        events->thread_quit = 0;
        pthread_mutex_destroy(&(events->mutex));
    }

    events->head = 0;
    events->tail = 0;
    events->queue_size = 0;
    SDL_free(events->queue);
    events->queue = NULL;

    SDL_Log(DEBUG1, "exit");
}

/* Initialize the events sources */
static int events_init(SDL_VideoDevice *device)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    events_t *events = &(hidden->events);

    SDL_Log(DEBUG1, "entered");

    events->head = 0;
    events->tail = 0;
    events->queue_size = 128;
    events->queue = (SDL_Event*)SDL_malloc(sizeof(SDL_Event) * events->queue_size);
    if(events->queue) {
        if(pthread_mutex_init(&(events->mutex), NULL) == 0) {
            if(pthread_create(&(events->thread), NULL, events_thread, device) == 0) {
                SDL_Log(DEBUG1, "success");
                return 0;   /* sucess */
            }
            pthread_mutex_destroy(&(events->mutex));
        }
        SDL_free(events->queue);
        events->queue = NULL;
        events->queue_size = 0;
    }

    SDL_Log(ERROR, "failed");
    return -1;
}


/*------------------------------------------------------------------------------
* Interface functions
------------------------------------------------------------------------------*/

/* BB driver bootstrap functions */
static int BB_Available(void)
{
    return 1;
}

static void BB_DeleteDevice(SDL_VideoDevice *device)
{
    if(device) {
        if(device->hidden) {
            SDL_free(device->hidden);
        }
        SDL_free(device);
    }
}

static SDL_VideoDevice *BB_CreateDevice(int devindex)
{
    SDL_VideoDevice *device;

    SDL_Log(DEBUG1, "entered, devindex=%d", devindex);

    device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
    if(device) {
        device->hidden = (struct SDL_PrivateVideoData *)SDL_calloc(1, sizeof(*device->hidden));
        if(device->hidden) {
            device->VideoInit = BB_VideoInit;
            device->SetVideoMode = BB_SetVideoMode;
            device->VideoQuit = BB_VideoQuit;
            device->UpdateRects = BB_UpdateRects;
            device->PumpEvents = BB_PumpEvents;
            device->AllocHWSurface = BB_AllocHWSurface;
            device->BlitHWSurface = BB_BlitHWSurface;
            device->FreeHWSurface = BB_FreeHWSurface;
            device->free = BB_DeleteDevice;

            return device; /* success */
        }
        SDL_free(device);
    }

    SDL_Log(ERROR, "failed");
    return NULL; /* fail */
}

VideoBootStrap BB_bootstrap = {
    BB_DRIVER_NAME, "SDL BlackBerry (libscreen) video driver",
    BB_Available, BB_CreateDevice
};

/* BB driver interface functions */
static int BB_VideoInit(SDL_VideoDevice *device, SDL_PixelFormat *vformat)
{

    SDL_Log(DEBUG1, "entered, device=0x%p", device);

    if(screen_init(device) != 0) {
        SDL_Log(ERROR, "screen initialisation failed!");
        return -1;
    }

    if(app_window_init(device) != 0) {
        SDL_Log(ERROR, "application window initialisation failed!");
        screen_quit(device);
        return -1;
    }

    if(keyboard_init(device) != 0) {
        SDL_Log(ERROR, "keyboard initialisation failed!");
        app_window_quit(device);
        screen_quit(device);
        return -1;
    }

    if(events_init(device) != 0) {
        SDL_Log(ERROR, "events initialisation failed!");
        keyboard_quit(device);
        app_window_quit(device);
        screen_quit(device);
        return -1;
    }

    return 0;
}

static SDL_Surface *BB_SetVideoMode(SDL_VideoDevice *device, SDL_Surface *current,
                int width, int height, int bpp, Uint32 flags)
{
    SDL_Surface *surface;

    SDL_Log(DEBUG1, "entered, device=0x%p current=0x%p width=%d height=%d bpp=%d flags=0x%x",
        device, current, width, height, bpp, flags);

    if(current) {
        SDL_FreeSurface(current);
    }

    if(width > device->info.current_w || height > device->info.current_h || bpp != 32) {
        SDL_Log(ERROR, "unsupported format %dx%dx%d", width, height, bpp);
        return NULL;
    }

    surface = SDL_CreateRGBSurface(flags | SDL_HWACCEL, width, height, bpp, 0, 0, 0, 0);

    if(!surface) {
        SDL_Log(ERROR, "failed");
    }

    return surface;
}

static void BB_UpdateRects(SDL_VideoDevice *device, int numrects, SDL_Rect *rects)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    app_window_t *app_win = &(hidden->app_win);

    if(device->screen) {
        pthread_mutex_lock(&(app_win->mutex));
        app_win->upd_pend += numrects;
        pthread_cond_signal(&(app_win->cond));
        pthread_mutex_unlock(&(app_win->mutex));
    }
}

static void BB_VideoQuit(SDL_VideoDevice *device)
{
    SDL_Log(DEBUG1, "entered, device=0x%p", device);

    if(device->screen) {
        SDL_FreeSurface(device->screen);
        device->screen = NULL;
    }

    events_quit(device);
    keyboard_quit(device);
    app_window_quit(device);
    screen_quit(device);
}

static void BB_PumpEvents(SDL_VideoDevice *device)
{
    events_t *events = &(device->hidden->events);
    int more_events = 1;

    while(more_events) {
        pthread_mutex_lock(&(events->mutex));
        if(events->head == events->tail) {
            /* Internal events queue is empty */
            more_events = 0;
        } else {
            if(SDL_PushEvent(&(events->queue[events->head])) < 0) {
                /* SDL events queue is full */
                more_events = 0;
            } else {
                events->head = (events->head + 1) % events->queue_size;
            }
        }
        pthread_mutex_unlock(&(events->mutex));
    }
}

static void BB_FreeHWSurface(SDL_VideoDevice *device, SDL_Surface *surface)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    app_window_t *app_win = &(hidden->app_win);

    SDL_Log(DEBUG1, "entered, surface=0x%p", surface);

    if(!surface) {
        return;
    }

    if(surface == device->screen) {
        pthread_mutex_lock(&(app_win->mutex));
        app_win->upd_pend = 0;
    }

    if(surface->hwdata) {
        screen_destroy_pixmap_buffer(surface->hwdata->pixmap);
        screen_destroy_pixmap(surface->hwdata->pixmap);
        SDL_free(surface->hwdata);
        surface->hwdata = NULL;
        surface->pitch = 0;
        surface->pixels = NULL;
    }

    if(surface == device->screen) {
        device->screen = NULL;
        pthread_mutex_unlock(&(app_win->mutex));
    }
}

static int BB_AllocHWSurface(SDL_VideoDevice *device, SDL_Surface *surface)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    private_hwdata *hwdata;

    int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_NATIVE;
    int format = SCREEN_FORMAT_RGBA8888;
    void *pixels;
    int pitch = 0;
    int ret = 0;
    int size[2] = { surface->w, surface->h };

    SDL_Log(DEBUG1, "entered, surface=0x%p", surface);

    hwdata = SDL_calloc(1, sizeof(private_hwdata));
    if(hwdata) {
        if(screen_create_pixmap(&(hwdata->pixmap), hidden->screen_ctx) == 0) {
            ret |= screen_set_pixmap_property_iv(hwdata->pixmap, SCREEN_PROPERTY_USAGE, &usage);
            ret |= screen_set_pixmap_property_iv(hwdata->pixmap, SCREEN_PROPERTY_FORMAT, &format);
            ret |= screen_set_pixmap_property_iv(hwdata->pixmap, SCREEN_PROPERTY_BUFFER_SIZE, size);
            if(ret == 0) {
                if(screen_create_pixmap_buffer(hwdata->pixmap) == 0) {
                    ret |= screen_get_pixmap_property_pv(hwdata->pixmap,
                        SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&(hwdata->buffer));
                    ret |= screen_get_buffer_property_pv(hwdata->buffer, SCREEN_PROPERTY_POINTER, &pixels);
                    ret |= screen_get_buffer_property_iv(hwdata->buffer, SCREEN_PROPERTY_STRIDE, &pitch);
                    if(ret == 0) {
                        int attribs[] = {
                            SCREEN_BLIT_COLOR, 0xff000000, /* black */
                            SCREEN_BLIT_END };

                        SDL_Log(DEBUG1, "success, surface=0x%p hwdata=0x%p pixels=0x%p pitch=%d",
                            surface, hwdata, pixels, pitch);

                        surface->hwdata = hwdata;
                        surface->pixels = pixels;
                        surface->pitch = (Uint16)pitch;
                        screen_fill(hidden->screen_ctx, hwdata->buffer, attribs);

                        return 0;   /* sucess */
                    }
                    screen_destroy_pixmap_buffer(hwdata->pixmap);
                }
            }
            screen_destroy_pixmap(hwdata->pixmap);
        }
        SDL_free(hwdata);
    }
    surface->hwdata = NULL;
    surface->pitch = 0;
    surface->pixels = NULL;

    SDL_Log(ERROR, "fail, surface=0x%p", surface);

    return -1;  /* fail */
}

static int BB_BlitHWSurface(SDL_VideoDevice *device,
                SDL_Surface *src, SDL_Rect *srcrect,
                SDL_Surface *dst, SDL_Rect *dstrect)
{
    SDL_PrivateVideoData *hidden = device->hidden;
    app_window_t *app_win = &(hidden->app_win);
    int alpha = src->format->alpha;
    int ret = 0;

    int transparency = (src->flags & SDL_SRCALPHA ?
        SCREEN_TRANSPARENCY_SOURCE_OVER : SCREEN_TRANSPARENCY_NONE);

    int attribs[] = {
        SCREEN_BLIT_SOURCE_X, (int)(srcrect->x),
        SCREEN_BLIT_SOURCE_Y, (int)(srcrect->y),
        SCREEN_BLIT_SOURCE_WIDTH, (int)(srcrect->w),
        SCREEN_BLIT_SOURCE_HEIGHT, (int)(srcrect->h),
        SCREEN_BLIT_DESTINATION_X, (int)(dstrect->x),
        SCREEN_BLIT_DESTINATION_Y, (int)(dstrect->y),
        SCREEN_BLIT_DESTINATION_WIDTH, (int)(dstrect->w),
        SCREEN_BLIT_DESTINATION_HEIGHT, (int)(dstrect->h),
        SCREEN_BLIT_GLOBAL_ALPHA, alpha,
        SCREEN_BLIT_TRANSPARENCY, transparency,
        SCREEN_BLIT_END };

    if(dst == device->screen) {
        pthread_mutex_lock(&(app_win->mutex));
    }

    if(screen_blit(hidden->screen_ctx, dst->hwdata->buffer, src->hwdata->buffer, attribs) != 0) {
        SDL_Log(ERROR, "screen_blit() failed, err=%d (%s)", errno, strerror(errno));
        ret = -1;
    }

    if(dst == device->screen) {
        pthread_mutex_unlock(&(app_win->mutex));
    }

    return ret;
}

